/*
 * @Author: zi.yang
 * @Date: 2022-12-03 15:56:30
 * @LastEditors: zi.yang
 * @LastEditTime: 2022-12-04 21:32:47
 * @Description: Resolve Promise
 * @FilePath: /promise/lib/utils/resolvePromise.ts
 */
import { isFunction, isNotNull, isObject } from './common';
import { throwTypeError } from './throwError';

/**
 * @param promise
 * @param x
 * @returns
 */
function resolvePromise<T>(promise: ToyPromiseInterface<T>, x: ToyPromiseInterface<T> | any): void {
  // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
  if (promise === x) {
    promise.reject(throwTypeError('A promise cannot be resolved with itself.'));
    return;
  }
  if (isNotNull(x) && (isObject(x) || isFunction(x))) {
    // 2.2.2.3 it must not be called more than once.
    let called = false;
    // 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
    try {
      // 2.3.3.1 Let then be x.then. [3.5]
      const { then } = x;
      if (isFunction(then)) {
        // 2.3.3.3 If then is a function, call it with x as this,
        // first argument resolvePromise, and second argument rejectPromise, where:
        then.call(
          x,
          (y: unknown) => {
            if (called) return;
            called = true;
            resolvePromise(promise, y);
          },
          (r: unknown) => {
            if (called) return;
            called = true;
            promise.reject(r);
          },
        );
      } else {
        promise.resolve(x);
      }
    } catch (err) {
      if (called) return;
      called = true;
      // 2.3.3.3.4 If calling then throws an exception e,
      // - If resolvePromise or rejectPromise have been called, ignore it.
      // - Otherwise, reject promise with e as the reason.
      promise.reject(err);
    }
  } else {
    // 2.3.4 If x is not an object or function, fulfill promise with x.
    promise.resolve(x);
  }
}
export default resolvePromise;
